// shell.c
// Taco Shell v1.0 (tsh)
// By Brad Pineau

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#define PROMPT "Taco Shell"
#define FALSE 0
#define TRUE 1
#define NORMALMODE 0
#define CONCURRENTMODE 1
#define STDIN 0
#define STDOUT 1
#define DEFAULT 2

// structure to hold the command name and arguments
typedef struct command_t {
  char *name;
  int argc;
  char **argv;
} CommandType;

// declaration of functions used in program
char* getCommandPath(char* command);
void trim(char* name);
int runCommand(CommandType* command);
int runConcurrentCommand(CommandType* command);
int runPipedCommands(CommandType* command, CommandType* command2);
int runRedirectedCommand(CommandType* command, char* file, int fileid);


char *path;
char *ptr;

char *input;
int pid;
int status;

int redirectionType;

// returns the full path of a command
// returns NULL if the command is not found on the path
char* getCommandPath(char* command) {

  FILE *fpin;
  char p[1024];
  char *temppath;
  char *temp = (char *)calloc(1024, sizeof(char));

  path = (char *) getenv("PATH");

  strcpy ( p, path );
  path = strtok ( p, ":" );   // Find the first PATH
  while ( path != NULL ) {
    strcpy ( temp, path );  // Save a copy of substring
    strcat ( temp, "/" );     // Form a path and file name
    strcat ( temp, command );
    if ( ( fpin = fopen ( temp, "r" ) ) == NULL ) {
      temp[0] = '\0';  // Start over again
      path = strtok ( ( char * ) 0, ":" );  // Try next path
    } else {
      break;  // Must have a good fpin
    }
  }
  if ( fpin == NULL ) {
    return NULL;
  } else {
    return temp;
  }

}


// this function was found on groups.google.com
// there was no author's name with the code...
void trim(char* name) {
  char* p = name ;
  size_t n ;
  while (isspace(*p))
    ++p ;
  if ((n = strlen(p)) > 0)
    while (isspace(p[n-1]))
      --n ;
  ((char*)memmove(name, p, n))[n] = '\0' ;
}

// the main program...
int main() {

  CommandType *command = (CommandType*) malloc(sizeof(CommandType));
  CommandType *command2 = (CommandType*) malloc(sizeof(CommandType));
  int count;
  int runmode;
  int piped;
  char* redirectionFilename;
  char* buffer = (char*)calloc(1024, sizeof(char));

  while(TRUE){
    piped = FALSE;
    redirectionType = DEFAULT;
    input = (char *)calloc(1024, sizeof(char));
    redirectionFilename = (char *)calloc(1024, sizeof(char));
    
    // prinit the prompt 
    printf("%s - (%s)> ", PROMPT, getcwd(buffer, 1024));

    gets(input);
    trim(input);
    // start over if a blank line happens
    if ((strcmp(input, "") != 0) && (strcmp(input, ".") != 0) && (strcmp(input, "..") != 0)) {
      // checks of exit or quit, and terminates program if seen
      if ((strcmp(input, "quit")== 0) || (strcmp(input, "exit")== 0)) {
        printf("Terminating Shell...\n");
        return 0;
      }
  
      // initialize argv array
      command->argv = (char**)calloc(256, sizeof(char*)); 
      command2->argv = (char**)calloc(256, sizeof(char*)); 
     
      // parses the command line
      ptr = strtok(input, " ");
      command->name = ptr;
      command->argv[0] = ptr;
      command->argc = 1;
      count = 1;
      runmode = NORMALMODE;
      ptr = strtok (NULL, " ");

        while (ptr != NULL) {
          //command->argv[count++] = ptr;
          if (strcmp(ptr, "|")==0) {
            // pipe found
            if (piped) {
              printf("Error: Second level piping not supported.\n");
            } else {
              // set up parser to read into the 2nd command
              piped = TRUE;
              ptr = strtok (NULL, " ");
              if (ptr != NULL) {
                command2->name = ptr;
                command2->argv[0] = ptr;
                command2->argc = 1;
                count = 1; 
              } else {
                printf("Error: No command to pipe.\n"); 
              }
            }
          } else if (strcmp(ptr, ">")==0) {
            ptr = strtok (NULL, " ");
            if (ptr != NULL) {
              redirectionFilename = ptr;
              redirectionType = STDOUT;              
              ptr = NULL; // used to break while loop              
            } else {
              printf("Error: No file specified for redirection.\n");
            }
        
          } else if (strcmp(ptr, "<")==0) {
            ptr = strtok (NULL, " ");
            if (ptr != NULL) {
              redirectionFilename = ptr;
              redirectionType = STDIN;
              ptr = NULL; // used to break while loop
            } else {
              printf("Error: No file specified for redirection.\n");
            }
           
          } else { 
            if (piped) {
              command2->argv[count++] = ptr;
              command2->argc++;
            } else {
              command->argv[count++] = ptr;
              command->argc++;
            }
          }
          ptr = strtok (NULL, " ");
        }

      if (piped) {
        runPipedCommands(command, command2);
      } else if (redirectionType != DEFAULT) {
        runRedirectedCommand(command, redirectionFilename, redirectionType);
      } else {


        if (strcmp(command->argv[command->argc-1],"&")==0) {
          // & found, remove it from argv list
          runmode = CONCURRENTMODE;
          command->argv[command->argc-1] = NULL;
        }
     
        // check for built-in commands
        if (strcmp(command->argv[0], "echo") == 0) {
          // echo the commandline tokens
          int i;
          for (i = 1; i < command->argc; i++) {
            printf("%s ", command->argv[i]);
          }
          printf("\n");
        } else if (strcmp(command->argv[0],"cd") == 0) {
          // change directory
          if (command->argc == 2) {
            if (chdir(command->argv[1]) != 0) {
              printf("Error: Invalid directory.\n");
            }
          } else {
            printf("Error: Invalid use of cd command.\n");
          }
        } else if (strcmp(command->argv[0],"getenv") == 0) {
          if (command->argc == 2) {
            char* variable = (char *) getenv(command->argv[1]);
            if (variable == NULL) {
              printf("Error: Invalid environment variable.\n");
            } else {
              printf("%s\n", variable);
            }
          } else {
            printf("Error: Invalid use of getenv command.\n");
          }

        } else if (strcmp(command->argv[0],"setenv") == 0) {
          if (command->argc == 3) {
            char* value = (char*)calloc(1024,sizeof(char));
            strcpy(value, command->argv[1]);
            strcat(value, "=");
            strcat(value, command->argv[2]);
            putenv(value);
          } else {
            printf("Error: Invalid use of getenv command.\n");
          }

    
        } else {
          // run the command as regular command....
          if (runmode == NORMALMODE) {
            runCommand(command);
          } else if (runmode == CONCURRENTMODE) {
            runConcurrentCommand(command);
          }
        }
      } // if (piped)
    }
  }
}

// runs a command normally
int runCommand(CommandType* command) {

  char* filename = getCommandPath(command->name);
  if (filename == NULL) {
    printf("Command not found.\n");
    return 1;
  }

  // creates child process
  pid = fork();
  // checks for error
  if (pid < 0) {
    printf("Error in the fork process.\n");
    return 1;
  } else if (pid == 0) {
    // Child code
    execv(filename, command->argv);
    return 1;
  } else {
    // Parent waits
    while (wait(&status) != pid);
  }
}

// runs a command as a background process
int runConcurrentCommand(CommandType* command) {

  char* filename = getCommandPath(command->name);
  if (filename == NULL) {
    printf("Command not found.\n");
    return 1;
  }

  // creates child process
  pid = fork();
  // checks for error
  if (pid < 0) {
    printf("Error in the fork process.\n");
    return 1;
  } else if (pid == 0) {
    // Child code
    execv(filename, command->argv);
    return 1;
  } else {
    // Parent doesn't wait
    //while (wait(&status) != pid);
  }
}

// pipes one command into another
int runPipedCommands(CommandType* command, CommandType* command2) {

  char* filename = getCommandPath(command->name);
  if (filename == NULL) {
    printf("%s command not found.\n", command->name);
    return 1;
  }

  char* filename2 = getCommandPath(command2->name);
  if (filename2 == NULL) {
    printf("%s command not found.\n", command2->name);
    return 1;
  }

  int fd[2];
  int pid, pid2;

  if (pipe(fd) == -1) {
    printf("Error: Pipe failed.\n");
    return 1;
  }
  if ((pid = fork()) < 0) {
    printf("Error: Fork failed.\n");
    return 1;
  }
  
  if (pid == 0) {
    // child process
    close(fd[1]);
    dup2(fd[0], 0);
    close(fd[0]);
    execv(filename2, command2->argv);
    printf("Error: execv failed.\n");
    return 1;
  } else {
    // parent process
    // need to fork again, so the shell isn't replaced
    if ((pid2 = fork()) < 0) {
      printf("Error: Fork failed.\n");
      return 1;
    }
    if (pid2 == 0) {
      // child process
      close(fd[0]);
      dup2(fd[1], 1);
      close(fd[1]);
      execv(filename, command->argv);
      printf("Error: execv failed.\n");
      return 1;
    } else {
      // parent process (the shell)
      close(fd[0]);
      close(fd[1]);
      while (wait(&status) != pid2);
    }
  }
  return 0; 
}

// redirects STDIN or STDOUT for a command
int runRedirectedCommand(CommandType* command, char* file, int fileid) {

  char* filename = getCommandPath(command->name);
  
  if (filename == NULL) {
    printf("%s command not found.\n", command->name);
    return 1;
  }

  int pid;
  if ((pid = fork()) < 0) {
    printf("Error: Fork failed.\n");
    return 1;
  }
  if (pid == 0) {
    // child process

    int fid;
    if (fileid == STDIN) {
      fid = open(file, O_RDONLY, 0600);
    } else if (fileid == STDOUT) {
      fid = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0600);
    }
    dup2(fid, fileid);
    close(fid);

    execv(filename, command->argv);
    printf("Error: execv failed.\n");
    return 1;    
  } else {
    // parent process (shell)
    while (wait(&status) != pid);    
  }

}

